package com.xl.rentkeeper.db.orm.util;

import android.content.ContentValues;
import android.database.Cursor;
import android.util.Log;

import com.xl.rentkeeper.db.orm.ORMap;
import com.xl.rentkeeper.db.orm.dsl.ForceDB;
import com.xl.rentkeeper.db.orm.dsl.Ignore;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;

public class ReflectionUtil {

    public static List<Field> getTableFields(Class table) {
        List<Field> fieldList = FiledsCache.getFields(table);
        if (fieldList != null) return fieldList;

        Log.d("Sugar", "Fetching properties");
        List<Field> typeFields = new ArrayList<Field>();

        getAllFields(typeFields, table);

        List<Field> toStore = new ArrayList<Field>();
        for (Field field : typeFields) {
            if (field.isAnnotationPresent(ForceDB.class) || !field.isAnnotationPresent(Ignore.class) && !Modifier.isStatic(field.getModifiers()) && !Modifier.isTransient(field.getModifiers())) {
                toStore.add(field);
            }
        }

        FiledsCache.setFields(table, toStore);
        return toStore;
    }

    private static List<Field> getAllFields(List<Field> fields, Class<?> type) {
        Collections.addAll(fields, type.getDeclaredFields());

        if (type.getSuperclass() != null) {
            fields = getAllFields(fields, type.getSuperclass());
        }

        return fields;
    }

    public static void addFieldValueToColumn(ContentValues values, Field column, Object object) {
        column.setAccessible(true);
        Class<?> columnType = column.getType();
        try {
            String columnName = NamingHelper.toSQLName(column);
            Object columnValue = column.get(object);
            if (columnName.equalsIgnoreCase(ORMap.TableKeyId)) {
                return;
            }
            if (columnType.equals(Short.class) || columnType.equals(short.class)) {
                values.put(columnName, (Short) columnValue);
            } else if (columnType.equals(Integer.class) || columnType.equals(int.class)) {
                values.put(columnName, (Integer) columnValue);
            } else if (columnType.equals(Long.class) || columnType.equals(long.class)) {
                values.put(columnName, (Long) columnValue);
            } else if (columnType.equals(Float.class) || columnType.equals(float.class)) {
                values.put(columnName, (Float) columnValue);
            } else if (columnType.equals(Double.class) || columnType.equals(double.class)) {
                values.put(columnName, (Double) columnValue);
            } else if (columnType.equals(Boolean.class) || columnType.equals(boolean.class)) {
                values.put(columnName, (Boolean) columnValue);
            } else if (Timestamp.class.equals(columnType)) {
                try {
                    values.put(columnName, ((Timestamp) column.get(object)).getTime());
                } catch (NullPointerException e) {
                    values.put(columnName, (Long) null);
                }
            } else if (Date.class.equals(columnType)) {
                try {
                    values.put(columnName, ((Date) column.get(object)).getTime());
                } catch (NullPointerException e) {
                    values.put(columnName, (Long) null);
                }
            } else if (Calendar.class.equals(columnType)) {
                try {
                    values.put(columnName, ((Calendar) column.get(object)).getTimeInMillis());
                } catch (NullPointerException e) {
                    values.put(columnName, (Long) null);
                }
            } else if (columnType.equals(byte[].class)) {
                if (columnValue == null) {
                    values.put(columnName, "".getBytes());
                } else {
                    values.put(columnName, (byte[]) columnValue);
                }
            } else {
                if (columnValue == null) {
                    values.putNull(columnName);
                } else {
                    values.put(columnName, String.valueOf(columnValue));
                }
            }

        } catch (IllegalAccessException e) {
            Log.e("Sugar", e.getMessage());
        }
    }

    public static void setFieldValueFromCursor(Cursor cursor, Field field, Object object) {
        field.setAccessible(true);
        try {
            Class fieldType = field.getType();
            String colName = NamingHelper.toSQLName(field);
            int columnIndex = cursor.getColumnIndex(colName);
            if (cursor.isNull(columnIndex)) {
                return;
            }
            if (colName.equalsIgnoreCase(ORMap.TableKeyId)) {
                long cid = cursor.getLong(columnIndex);
                field.set(object, Long.valueOf(cid));
            } else if (fieldType.equals(long.class) || fieldType.equals(Long.class)) {
                field.set(object,
                        cursor.getLong(columnIndex));
            } else if (fieldType.equals(String.class)) {
                String val = cursor.getString(columnIndex);
                field.set(object, val != null && val.equals("null") ? null : val);
            } else if (fieldType.equals(double.class) || fieldType.equals(Double.class)) {
                field.set(object,
                        cursor.getDouble(columnIndex));
            } else if (fieldType.equals(boolean.class) || fieldType.equals(Boolean.class)) {
                field.set(object,
                        cursor.getString(columnIndex).equals("1"));
            } else if (field.getType().getName().equals("[B")) {
                field.set(object,
                        cursor.getBlob(columnIndex));
            } else if (fieldType.equals(int.class) || fieldType.equals(Integer.class)) {
                field.set(object,
                        cursor.getInt(columnIndex));
            } else if (fieldType.equals(float.class) || fieldType.equals(Float.class)) {
                field.set(object,
                        cursor.getFloat(columnIndex));
            } else if (fieldType.equals(short.class) || fieldType.equals(Short.class)) {
                field.set(object,
                        cursor.getShort(columnIndex));
            } else if (fieldType.equals(Timestamp.class)) {
                long l = cursor.getLong(columnIndex);
                field.set(object, new Timestamp(l));
            } else if (fieldType.equals(Date.class)) {
                long l = cursor.getLong(columnIndex);
                field.set(object, new Date(l));
            } else if (fieldType.equals(Calendar.class)) {
                long l = cursor.getLong(columnIndex);
                Calendar c = Calendar.getInstance();
                c.setTimeInMillis(l);
                field.set(object, c);
            } else if (fieldType.equals(byte[].class)) {
                byte[] bytes = cursor.getBlob(columnIndex);
                if (bytes == null) {
                    field.set(object, "".getBytes());
                } else {
                    field.set(object, cursor.getBlob(columnIndex));
                }
            } else if (Enum.class.isAssignableFrom(fieldType)) {
                try {
                    Method valueOf = field.getType().getMethod("valueOf", String.class);
                    String strVal = cursor.getString(columnIndex);
                    Object enumVal = valueOf.invoke(field.getType(), strVal);
                    field.set(object, enumVal);
                } catch (Exception e) {
                    Log.e("Sugar", "Enum cannot be read from Sqlite3 database. Please check the type of field " + field.getName());
                }
            } else
                Log.e("Sugar", "Class cannot be read from Sqlite3 database. Please check the type of field " + field.getName() + "(" + field.getType().getName() + ")");
        } catch (IllegalArgumentException e) {
            Log.e("field set error", e.getMessage());
        } catch (IllegalAccessException e) {
            Log.e("field set error", e.getMessage());
        }
    }

    public static void setFieldValueForId(Object object, Long value) {
        try {
            Field field = object.getClass().getField(ORMap.TableKeyId);
            field.setAccessible(true);
            field.set(object, value);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }

    public static long getFieldValueForId(Object object) {
        try {
            Field field = object.getClass().getField(ORMap.TableKeyId);
            field.setAccessible(true);
            return (long) field.get(object);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
        return 0;
    }
}
